package com.gframework.core.middleware;

import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;

import org.apache.http.client.config.RequestConfig;
import org.apache.http.config.ConnectionConfig;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.scheduling.annotation.Scheduled;

/**
 * HttpClient配置.
 * 
 * <p>HttpClient的滥用是影响性能很大的因素之一，本身HttpClient包括HttpAsyncClient
 * 都是线程安全，可复用，有线程池的一个对象，每个地方自己去创建，会有很多重复代码和难以管理的
 * 连接池操作。同时连接池也是需要定期过期连接的，因此专门设置此类，统一配置HttpClient类对象。
 * 
 * <p>一个工程中只能有一个ConnectionManager，但是可以有多个HttpClient，本类又有一个
 * 默认的HttpClient对象，如果你需要更加多的自定义操作，那么可以在本类中配置新的HttpClient对象。
 * 
 * @since 1.0.0
 * @author Ghwolf
 * @see CustomRedirectHttpClientWrapper
 */
@Configuration(proxyBeanMethods=false)
@EnableConfigurationProperties(CommonHttpClientProperties.class)
public class HttpClientConfig implements DisposableBean{

	/**
	 * 清理过期连接时间间隔:{@value} ms
	 */
	private static final int CLEAR_DELAY = 1000 * 60 * 10;
	/**
	 * httpclient最大空闲时间：{@value} 秒，超过了会被回收。
	 */
	private static final int IDLE_SECONDS = 60;
	/**
	 * 配置信息
	 */
	private final CommonHttpClientProperties clientProperties ;
	/**
	 * 连接池对象
	 */
	private PoolingHttpClientConnectionManager connectionManager;
	
	
	/**
	 * 默认的httpClient对象，放到static用于方便获取.
	 */
	private static CloseableHttpClient defaultHttpClient ;
	/**
	 * 可拦截重定向操作的httpClient对象，放到static用于方便获取.
	 */
	private static CustomRedirectHttpClientWrapper customRedirectHttpClient ;

	public HttpClientConfig(CommonHttpClientProperties clientProperties){
		this.clientProperties = clientProperties;
	}

	@Override
	public void destroy() throws Exception {
		defaultHttpClient = null ;
		customRedirectHttpClient = null ;
	}
	
	/**
	 * 取得默认的HttpClient对象，如果还没有初始化完毕就调用，则抛出异常
	 */
	public static CloseableHttpClient getDefaultHttpClient() {
		if (defaultHttpClient == null) {
			throw new BeanInitializationException("defaultHttpClient 还没有初始化！");
		}
		return defaultHttpClient;
	}
	/**
	 * 取得可拦截重定向操作的httpClient对象，如果还没有初始化完毕就调用，则抛出异常
	 */
	public static CustomRedirectHttpClientWrapper getCustomRedirectHttpClient() {
		if (customRedirectHttpClient == null) {
			throw new BeanInitializationException("customRedirectHttpClient 还没有初始化！");
		}
		return customRedirectHttpClient;
	}
	
	
	/**
	 * 创建一个具有指定连接池大小和同一路由最大连接限制的，{@code UTF-8}编码的，可自动回收过期连接的连接池对象.
	 * <p>该对象绑定当前bean的生命周期，如果HttpClientConfig销毁了，那么此连接池对象也会被销毁并关闭。
	 */
	@Bean(value = "httpclient-connectionManager", destroyMethod = "close")
	public PoolingHttpClientConnectionManager getConnectionManager() {
		ConnectionConfig connectionConfig = ConnectionConfig.custom().setCharset(StandardCharsets.UTF_8).build();

		PoolingHttpClientConnectionManager manager = new PoolingHttpClientConnectionManager();
		manager.setValidateAfterInactivity(this.clientProperties.getValidateAfterInactivity());
		manager.setDefaultMaxPerRoute(this.clientProperties.getMaxPoolSize());
		manager.setMaxTotal(this.clientProperties.getMaxPoolSize());
		manager.setDefaultConnectionConfig(connectionConfig);
		this.connectionManager = manager;
		return manager;
	}

	/**
	 * 具有默认配置的使用http协议的HttpClient对象
	 */
	@Primary
	@Bean(value = "default-httpClient", destroyMethod = "close")
	public CloseableHttpClient getDefaultHttpClient(
			@Qualifier("httpclient-connectionManager") PoolingHttpClientConnectionManager connectionManager) {
		if (defaultHttpClient != null) {
			throw new BeanInitializationException("重复初始化了 default-httpClient bean！");
		}
		defaultHttpClient = getBasicHttpClient();
		return defaultHttpClient;
	}
	
	/**
	 * 可自定义重定向拦截器的HttpClient对象，同时cookie不在全局缓存，而是局部缓存。
	 * 首先线程是threadlocal存储，线程之前互不干扰，同时你可以指定在任何时候清空当前线程缓存并决定后续的操作是否要进行cookie缓存。
	 */
	@Bean(value = "custom-redirect-httpClient", destroyMethod = "close")
	public CustomRedirectHttpClientWrapper getCustomRedirectHttpClient(
			@Qualifier("httpclient-connectionManager") PoolingHttpClientConnectionManager connectionManager) {
		if (customRedirectHttpClient != null) {
			throw new BeanInitializationException("重复初始化了 custom-redirect-httpClient bean！");
		}
		CustomRedirectStrategy reditrectStrategy = new CustomRedirectStrategy();
		CloseableHttpClient client = HttpClients.custom()
				.setDefaultRequestConfig(getBasicRequestConfig())
				.setConnectionManager(connectionManager)
				.setRedirectStrategy(reditrectStrategy)
				.setDefaultCookieStore(ThreadLocalCookieStore.getInstance())
				.build();
		customRedirectHttpClient = new CustomRedirectHttpClientWrapper(client,reditrectStrategy);
		return customRedirectHttpClient;
	}
	
	/**
	 * 一个基础的，简单默认配置的HttpClient对象
	 */
	private CloseableHttpClient getBasicHttpClient() {
		return HttpClients.custom()
				.setDefaultRequestConfig(getBasicRequestConfig())
				.setConnectionManager(connectionManager)
				.build();
	}
	/**
	 * 取得一个基础的，简单默认配置的RequestConfig类对象。
	 */
	private RequestConfig getBasicRequestConfig() {
		return RequestConfig.custom()
				.setConnectTimeout(this.clientProperties.getDefaultTimeout())
				.setSocketTimeout(this.clientProperties.getDefaultTimeout())
				.setConnectionRequestTimeout(this.clientProperties.getDefaultTimeout())
				.build();
	}

	/**
	 * 回收空闲连接定时器
	 */
	@Scheduled(initialDelay = CLEAR_DELAY, fixedDelay = CLEAR_DELAY)
	public void clear() {
		this.connectionManager.closeExpiredConnections();
		this.connectionManager.closeIdleConnections(IDLE_SECONDS, TimeUnit.SECONDS);
	}



}
